﻿using System;
using System.Linq;
using System.Reflection;
using Cfg.Attributes;
using Cfg.Exceptions;
using Cfg.Helpers;
using Cfg.ValidationAttributes;

namespace Cfg.ConfigValues
{
    public class ValueBuilder
    {
        private readonly Type _type;
        private readonly object[] _attributes;
        private readonly string _context;

        /// <summary>
        /// Original value in config file
        /// </summary>
        private readonly string _stringValue;

        /// <summary>
        /// Formatted value
        /// </summary>
        private readonly string _finalizedStringValue;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="finalizedString"></param>
        public ValueBuilder(Type type, string finalizedString)
            : this(type, null, null, finalizedString, finalizedString)
        {}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="attributes"></param>
        /// <param name="context"></param>
        /// <param name="stringValue"></param>
        /// <param name="finalizedString"></param>
        public ValueBuilder(Type type, object[] attributes, string context, string stringValue, string finalizedString)
        {
            _type = type;
            _attributes = attributes ?? new object[0];
            _context = context;

            _stringValue = stringValue;
            _finalizedStringValue = finalizedString;
        }

        /// <summary>
        /// Get attribute
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private T Attr<T>()
        {
            return _attributes.OfType<T>().FirstOrDefault();
        }

        /// <summary>
        /// Gets default value defined via DefaultAttribute
        /// </summary>
        /// <returns></returns>
        public object DefaultFromAttribute()
        {
            var defaultAttribute = Attr<DefaultAttribute>();
            if (defaultAttribute == null || (defaultAttribute.Contexts != null && defaultAttribute.Contexts.Length != 0 && !defaultAttribute.Contexts.Contains(_context)))
                return null;
            
            return defaultAttribute.DefaultValue;
        }

        /// <summary>
        /// Gets current types default value
        /// </summary>
        /// <returns></returns>
        public static object Default(Type type)
        {
            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="rawValue"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static T GetValue<T>(string rawValue, T defaultValue)
        {
            return GetValueInternal(rawValue, rawValue, defaultValue, true, null, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="rawValue"></param>
        /// <returns></returns>
        public static T GetValue<T>(string rawValue)
            {
            return GetValueInternal(rawValue, rawValue, default(T), false, null, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stringValue"></param>
        /// <param name="finalizedStringValue"></param>
        /// <param name="defaultValue"></param>
        /// <param name="defaultValueSet"></param>
        /// <param name="attributes"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        internal static T GetValueInternal<T>(string stringValue, string finalizedStringValue, T defaultValue, bool defaultValueSet, object[] attributes, string context)
        {
            if (string.IsNullOrEmpty(finalizedStringValue))
            {
                // note - default value is NOT validated
                if (defaultValueSet)
                    return defaultValue;
            }

            var type = typeof(T);

            // validate finalized string
            foreach (var attribute in attributes.OfType<CfgFinalizedStringValueValidatorBaseAttribute>())
            {
                attribute.Validate(finalizedStringValue, context);
            }
            
            T returnValue;

            if (type.IsInheritedFrom(typeof(ConfigValueBase)))
            {
                var tInstance = Activator.CreateInstance<T>();
                var cvb = tInstance as ConfigValueBase;
                if (cvb != null)
            {
                    cvb.ConfigValue = stringValue;
                    cvb.FinalizedStringValue = finalizedStringValue;
                    cvb.Attributes = attributes;
                cvb.Parse();
                }

                returnValue = tInstance;
            }
            else if (AppSettings.Settings.Parsers.ContainsKey(type))
            {
                returnValue = (T)AppSettings.Settings.Parsers[type].DynamicInvoke(finalizedStringValue);
            }
            /*
            else if (_type.HasParseExtension())
            {
                returnValue = Activator.CreateInstance(_type);
                var mi = _type.GetMethod("Parse", new[] {_type, typeof (string)});
                mi.Invoke(returnValue, new object[] {FinalizedStringValue});
            }
            **/
            else if (string.IsNullOrEmpty(finalizedStringValue))
            {
                returnValue = default(T);
            }
            else
            {
                try
                {
                    returnValue = (T)StringConverter.Convert(finalizedStringValue, type);
                }
                catch (TargetInvocationException)
                {
                    throw new InvalidFormatException(string.Format("Invalid format. {0} is not type of {1}", finalizedStringValue, type));
                }
            }

            foreach (var validator in attributes.OfType<CfgValueValidatorBaseAttribute>())
            {
                validator.Validate(returnValue, context);
            }

            return returnValue;
        }

        /// <summary>
        /// Get parsed value object
        /// </summary>
        /// <returns></returns>
        public object GetValue()
        {
            var defaultValue = DefaultFromAttribute();

            MethodInfo method = typeof(ValueBuilder).GetMethod("GetValueInternal", BindingFlags.Static | BindingFlags.NonPublic);
            MethodInfo generic = method.MakeGenericMethod(_type);
            try
            {
                return generic.Invoke(null, new [] {_stringValue, _finalizedStringValue, defaultValue, defaultValue != null, _attributes, _context});
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw ex.InnerException;

                throw;
            }
        }
    }
}